package net.xdclass.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.xxl.job.core.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.config.RabbitMQConfig;
import net.xdclass.constant.RedisKey;
import net.xdclass.controller.request.TrafficPageRequest;
import net.xdclass.controller.request.UseTrafficRequest;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.EventMessageType;
import net.xdclass.enums.PluginLevelEnum;
import net.xdclass.enums.TaskStateEnum;
import net.xdclass.exception.BizException;
import net.xdclass.feign.ProductFeignService;
import net.xdclass.feign.ShortLinkFeignService;
import net.xdclass.interceptor.LoginInterceptor;
import net.xdclass.manager.TrafficManager;
import net.xdclass.model.EventMessage;
import net.xdclass.model.TrafficDO;
import net.xdclass.model.TrafficTaskDO;
import net.xdclass.service.TrafficService;
import net.xdclass.service.TrafficTaskManager;
import net.xdclass.util.JsonData;
import net.xdclass.util.JsonUtil;
import net.xdclass.util.TimeUtil;
import net.xdclass.vo.ProductVO;
import net.xdclass.vo.TrafficVO;
import net.xdclass.vo.UseTrafficVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 功能描述
 *
 * @author ASUS
 * @date 2024/12/10  8:48
 */
@Service
@Slf4j
public class TrafficServiceImpl implements TrafficService {
    @Autowired
    private TrafficManager trafficManager;
    @Autowired
    private ProductFeignService productFeignService;
    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;
    @Autowired
    private TrafficTaskManager trafficTaskManager;
    @Autowired
    private RabbitMQConfig rabbitMQConfig;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private ShortLinkFeignService shortLinkFeignService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleTrafficMessage(EventMessage eventMessage) {
        String messageType = eventMessage.getEventMessageType();
        Long accountNo = eventMessage.getAccountNo();
        if (EventMessageType.PRODUCT_ORDER_PAY.name().equalsIgnoreCase(messageType)) {
            //订单已支付，新增流量
            String content = eventMessage.getContent();
            Map orderInfoMap = JsonUtil.json2Obj(content, Map.class);
            //还原订单商品信息
            String outTradeNo = (String) orderInfoMap.get("outTradeNo");
            Integer buyNum = (Integer) orderInfoMap.get("buyNum");
            String productStr = (String) orderInfoMap.get("product");
            ProductVO productVO = JsonUtil.json2Obj(productStr, ProductVO.class);
            log.info("商品信息{}", productVO);
            //流量包有效期
            LocalDateTime expiredDateTime = LocalDateTime.now().plusDays(productVO.getValidDay());
            // LocalDateTime 对象转换为 Date 对象。
            Date date = Date.from(expiredDateTime.atZone(ZoneId.systemDefault()).toInstant());
            //构建流ᰁ包对象
            TrafficDO trafficDO = TrafficDO.builder()
                    .accountNo(accountNo)
                    .dayLimit(productVO.getDayTimes() * buyNum)
                    .dayUsed(0)
                    .totalLimit(productVO.getTotalTimes())
                    .pluginType(productVO.getPluginType())
                    .level(productVO.getLevel())
                    .productId(productVO.getId())
                    .outTradeNo(outTradeNo)
                    .expiredDate(date).build();
            int rows = trafficManager.add(trafficDO);
            log.info("消费消息新增流ᰁ包:rows= {},trafficDO={}", rows, trafficDO);
        } else if (EventMessageType.TRAFFIC_FREE_INIT.name().equalsIgnoreCase(messageType)) {
            //新⽤户注册初始化免费流ᰁ包 user_init 唯⼀索引，避免重复消费
            Long productId = Long.valueOf(eventMessage.getBizId());
            JsonData jsonData = productFeignService.detail(productId);
            //指定泛型
            ProductVO productVO = jsonData.getData(new TypeReference<ProductVO>() {
            });
            TrafficDO trafficDO = TrafficDO.builder()
                    .accountNo(accountNo)
                    .dayLimit(productVO.getDayTimes())
                    .dayUsed(0)
                    .totalLimit(productVO.getTotalTimes())
                    .pluginType(productVO.getPluginType())
                    .level(PluginLevelEnum.FIRST.name())
                    .productId(productVO.getId())
                    .expiredDate(new Date())
                    .outTradeNo("user_init")
                    .build();
            trafficManager.add(trafficDO);
        }else if(EventMessageType.TRAFFIC_USED.name().equalsIgnoreCase(messageType)){
            //流量包使⽤，检查是否成功使⽤
            //检查task是否存在
            //检查短链是否成功
            //如果不成功则恢复流量包，删除缓存key
            //删除task（也可以更新状态，定时删除也⾏）
            Long trafficTaskId = Long.valueOf(eventMessage.getBizId());
            TrafficTaskDO trafficTaskDO = trafficTaskManager.findByIdAndAccountNo(trafficTaskId, accountNo);
            //⾮空 且 是锁定状态
            if(trafficTaskDO!=null && trafficTaskDO.getLockState().equalsIgnoreCase(TaskStateEnum.LOCK.name())){
                JsonData jsonData = shortLinkFeignService.check(trafficTaskDO.getBizId());
                if(jsonData.getCode()!=0){
                    log.error("创建短链失败，流量包回滚");
                    String useDate = TimeUtil.format(trafficTaskDO.getGmtModified(), "yyyy-MM-dd");
                    trafficManager.releaseUsedTimes(accountNo,trafficTaskDO.getTrafficId(),trafficTaskDO.getUseTimes(),useDate);
                    //恢复流量包，应该删除这个key
                    String totalTrafficTimesKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC,accountNo);
                    redisTemplate.delete(totalTrafficTimesKey);
                }
                trafficTaskManager.deleteByIdAndAccountNo(trafficTaskId,accountNo);
            }
        }


    }

    @Override
    public Map<String, Object> pageAvailable(TrafficPageRequest request) {
        IPage<TrafficDO> trafficDOIPage = trafficManager.pageAvailable(request.getPage(), request.getSize(), LoginInterceptor.threadLocal.get().getAccountNo());
        List<TrafficVO> trafficVOS = trafficDOIPage.getRecords().stream().map(item -> beanProcess(item)).collect(Collectors.toList());
        Map<String, Object> pageMap = new HashMap<>(3);
        pageMap.put("total_record", trafficDOIPage.getTotal());
        pageMap.put("total_page", trafficDOIPage.getPages());
        pageMap.put("current_data", trafficVOS);
        return pageMap;
    }

    @Override
    public TrafficVO detail(Long trafficId) {
        TrafficDO trafficDO = trafficManager.findByIdAndAccountNo(trafficId, LoginInterceptor.threadLocal.get().getAccountNo());
        if (trafficDO != null) {
            return beanProcess(trafficDO);
        }
        return null;
    }

    @Override
    public boolean deleteExpiredTraffic() {
        return trafficManager.deleteExpiredTraffic();
    }

    @Override
    public List<TrafficDO> selectAvailableTraffics(Long accountNo) {
        return trafficManager.selectAvailableTraffics(accountNo);
    }

    @Override
    public int addDayUsedTimes(Long accountNo, Long trafficId, Integer usedTimes) {
        return trafficManager.addDayUsedTimes(trafficId,accountNo,usedTimes);
    }

    @Override
    public int releaseUsedTimes(long accountNo, Long trafficId, Integer useTimes ,String useDate) {
        return trafficManager.releaseUsedTimes(accountNo,trafficId,useTimes,useDate);
    }

    @Override
    public int batchUpdateUsedTimes(Long accountNo, List<Long> unUpdatedTrafficIds) {
        return trafficManager.batchUpdateUsedTimes(accountNo,unUpdatedTrafficIds);
    }

    /**
     * 查询⽤户全部可可用流量包
     *遍历⽤户可⽤流量包
     *判断是否更新-⽤⽇期判断
     *没更新的流量包后加⼊【待更新集合】中
     *增加【今天剩余可⽤总次数】
     *已经更新的判断是否超过当天使⽤次数
     *如果没超过则增加【今天剩余可⽤总次数】
     *超过则忽略
     *更新⽤户今⽇流量包相关数据
     *扣减使⽤的某个流量包使⽤次数
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public JsonData reduce(UseTrafficRequest request) {
        Long accountNo = request.getAccountNo();
        //处理流量包，筛选出未更新流量包，当前使用的流量包
        UseTrafficVO useTrafficVO = processTrafficList(accountNo);
        log.info("今天可用总次数:{},当前使用流量包:{},未更新流量包:{}", useTrafficVO.getDayTotalLeftTimes(), useTrafficVO.getCurrentTrafficDO(),
                useTrafficVO.getUnUpdatedTrafficIds());
        if(useTrafficVO.getCurrentTrafficDO() == null) {
            return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }
        log.info("待更新流量包列表：{}", useTrafficVO.getUnUpdatedTrafficIds());
        if(useTrafficVO.getUnUpdatedTrafficIds().size()>0){
            //更新
            trafficManager.batchUpdateUsedTimes(accountNo, useTrafficVO.getUnUpdatedTrafficIds());
        }
        //先更新，再扣减当前使用的流量包
        int rows = trafficManager.addDayUsedTimes(useTrafficVO.getCurrentTrafficDO().getId(),accountNo, 1);
        //保存task
        TrafficTaskDO trafficTaskDO = TrafficTaskDO.builder().trafficId(useTrafficVO.getCurrentTrafficDO().getId()).bizId(request.getBizId())
                .accountNo(accountNo).lockState(TaskStateEnum.LOCK.name()).useTimes(1).build();
        trafficTaskManager.add(trafficTaskDO);

        if(rows != 1){
            throw new BizException(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }
        //往Redis设置流量包总次数，短链服务那边递减次数，如果有新增流量包，则删除这个key TODO
        long leftSeconds = TimeUtil.getRemainSecondsOneDay(new Date());
        String totalKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
        //减少1是减去此次的流量次数
        redisTemplate.opsForValue().set(totalKey, useTrafficVO.getDayTotalLeftTimes() - 1,
                leftSeconds, TimeUnit.SECONDS);

        //发送mq消息
        EventMessage usedTrafficEventMessage = EventMessage.builder().eventMessageType(EventMessageType.TRAFFIC_USED.name())
                .bizId(String.valueOf(trafficTaskDO.getId())).accountNo(accountNo).build();

        //发送延迟信息,⽤于异常回滚，数据最终⼀致性
        rabbitTemplate.convertAndSend(rabbitMQConfig.getTrafficEventExchange(),
                rabbitMQConfig.getTrafficReleaseDelayRoutingKey(),
                usedTrafficEventMessage);
        return JsonData.buildSuccess(useTrafficVO);
    }

    private UseTrafficVO processTrafficList(Long accountNo) {
        //查询⽤户全部可可用流量包
        List<TrafficDO> trafficDOS = trafficManager.selectAvailableTraffics(accountNo);
        if (CollectionUtils.isEmpty(trafficDOS)) {
            throw new BizException(BizCodeEnum.TRAFFIC_EXCEPTION);
        }
        //天剩余可⽤总次数 = 总次数-已⽤
        Integer dayTotalLeftTimes = 0;
        ////当前使⽤流量包
        TrafficDO currentTrafficDO = null;
        //没过期，且今天没更新的流量包
        List<Long> unUpdatedTrafficIds = new ArrayList<>();
        String today = DateUtil.format(new Date(), "yyyy-MM-dd");
        for (TrafficDO trafficDO : trafficDOS) {
            //判断是否更新-⽤⽇期判断
            if (today.equals(DateUtil.format(trafficDO.getGmtModified(), "yyyy-MM-dd"))) {
                //已经更新   每天剩余可用总次数 = 总次数 - 已用
                int dayLeftTimes = trafficDO.getDayLimit() - trafficDO.getDayUsed();
                dayTotalLeftTimes = dayTotalLeftTimes + dayLeftTimes;

                //选取当次使用流量包
                if(dayLeftTimes > 0 && currentTrafficDO == null){
                    currentTrafficDO = trafficDO;
                }
            }else {
                //未更新
               dayTotalLeftTimes = dayTotalLeftTimes + trafficDO.getDayLimit();
               unUpdatedTrafficIds.add(trafficDO.getId());
                //选取当次使用流量包
                if( currentTrafficDO == null){
                    currentTrafficDO = trafficDO;
                }
            }
        }
        UseTrafficVO useTrafficVO = new UseTrafficVO(dayTotalLeftTimes, currentTrafficDO, unUpdatedTrafficIds);
        return useTrafficVO;
    }

    private TrafficVO beanProcess(TrafficDO item) {
        TrafficVO trafficVO = new TrafficVO();
        BeanUtils.copyProperties(item, trafficVO);
        return trafficVO;
    }
}
